package com.stone.framework.core.util;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;

import com.auth0.jwt.Algorithm;
import com.auth0.jwt.JWTExpiredException;
import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.JWTVerifyException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stone.framework.core.exception.BusinessException;
import com.stone.framework.core.model.UserInfo;

public class Jwts {

    private static final String SECRET = "7f6db13e0dd1767391b9a01fe7df14ce";

    @SuppressWarnings("unchecked")
    public static String sign(UserInfo userInfo) {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> claims = mapper.convertValue(userInfo, Map.class);
        return sign(claims);
    }

    public static String sign(Map<String, Object> claims) {
        JWTSigner signer = new JWTSigner(SECRET);

        JWTSigner.Options options = new JWTSigner.Options();
        options.setAlgorithm(Algorithm.HS256).setExpirySeconds(60 * 60 * 16).setIssuedAt(true).setJwtId(true);

        return signer.sign(claims, options);
    }

    public static void verify(String token, boolean isExpiration) {
        if (isExpiration) {
            doVerify(token);
        } else {
            doVerifyNotExpiration(token);
        }
    }

    public static Map<String, Object> getPayload(String token) {
        return doVerify(token);
    }

    private static void doVerifyNotExpiration(String token) {
        JWTVerifierWrapper verifier = new JWTVerifierWrapper(SECRET);
        try {
            verifier.verifyNotExpiration(token);
        } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | SignatureException
            | IOException | JWTVerifyException e) {
            throw new BusinessException("token认证失败", e);
        }
    }

    private static Map<String, Object> doVerify(String token) throws BusinessException {
        JWTVerifier verifier = new JWTVerifier(SECRET);
        try {
            return verifier.verify(token);
        } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | SignatureException
            | IOException | JWTVerifyException e) {
            throw new BusinessException("token认证失败", e);
        }
    }

    private static class JWTVerifierWrapper {

        private JWTVerifier verifier;

        public JWTVerifierWrapper(String secret) {
            verifier = new JWTVerifier(secret);
        }

        public void verifyNotExpiration(final String token) throws NoSuchAlgorithmException, InvalidKeyException,
            IllegalStateException, IOException, SignatureException, JWTVerifyException {
            if (token == null || "".equals(token)) {
                throw new IllegalStateException("token not set");
            }
            final String[] pieces = token.split("\\.");
            if (pieces.length != 3) {
                throw new IllegalStateException("Wrong number of segments: " + pieces.length);
            }
            final JsonNode jwtHeader = decodeAndParse(pieces[0]);
            final Algorithm algorithm = getAlgorithm(jwtHeader);
            final JsonNode jwtPayload = decodeAndParse(pieces[1]);
            verifySignature(pieces, algorithm);
            verifyExpiration(jwtPayload);
        }

        private JsonNode decodeAndParse(final String b64String) {
            try {
                Method method = verifier.getClass().getDeclaredMethod("decodeAndParse", String.class);
                method.setAccessible(true);
                return (JsonNode)method.invoke(verifier, b64String);
            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e1) {
                return null;
            }
        }

        private Algorithm getAlgorithm(final JsonNode jwtHeader) {
            try {
                Method method = verifier.getClass().getDeclaredMethod("getAlgorithm", JsonNode.class);
                method.setAccessible(true);
                return (Algorithm)method.invoke(verifier, jwtHeader);
            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e1) {
                return null;
            }
        }

        private void verifySignature(final String[] pieces, final Algorithm algorithm) {
            try {
                Method method =
                    verifier.getClass().getDeclaredMethod("verifySignature", String[].class, Algorithm.class);
                method.setAccessible(true);
                method.invoke(verifier, pieces, algorithm);
            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e1) {
            }
        }

        /**
         * refresh_token 有效期1个小时
         * 
         * @param jwtClaims
         * @throws JWTExpiredException
         */
        private void verifyExpiration(final JsonNode jwtClaims) throws JWTExpiredException {
            final long expiration = jwtClaims.has("iat") ? jwtClaims.get("iat").asLong(0) : 0;
            if (expiration != 0 && LocalDateTime.now().minusHours(1L)
                .isAfter(LocalDateTime.ofInstant(Instant.ofEpochMilli(expiration), ZoneId.systemDefault()))) {
                throw new JWTExpiredException("jwt expired", expiration);
            }
        }

    }

}
